/**
 * @version v1.2.2
 * @date 2025-4-12
 * @desc 正式版本
 */
(function () {
    window.dhPlayerControl = Object.assign({
        videoWS: null,
        wsConnectCount: 0,
        wsSession: 0,
        videoList: {}, // 每次创建后，每个videoId 对应的数据类
        hwndList: {}, // 每次创建后对应的 {hwnd: videoId} 键值对
        callBackList: {},
        wsConnect: false,
        loginFlag: "LOGIN_PENDING",
        connectPort: [8000, 8001, 8002, 8003, 8004],
        curPortIndex: 0,
    }, window.dhPlayerControl || {}, {
        noCardPlayerFlag: false,
        DHPlayerVersion: '',
        pkgDHPlayerVerion: [2505151714], // 配套的插件版本号
        pluginLoginInfo: {},
        isPIframe: false,
		windowState: 'wsPending',
        dhMessage: { // 错误码对应错误信息描述
            701: {
                code: 701,
                message: "当前正在对讲，无法打开音频",
                i18nKey: 'video.player.error701',
                type: 'msg'
            },
            702: {
                code: 702,
                message: "当前设备正在对讲",
                i18nKey: 'video.player.error702',
                type: 'msg'
            },
            703: {
                code: 703,
                message: "当前其他设备正在对讲",
                i18nKey: 'video.player.error703',
                type: 'msg'
            },
            704: {
                code: 704,
                message: "抓图鉴权",
                i18nKey: 'video.player.error704',
                type: 'option'
            },
            705: {
                code: 705,
                message: "本地录像下载鉴权",
                i18nKey: 'video.player.error705',
                type: 'option'
            },
            706: {
                code: 706,
                message: "主/辅码流切换",
                i18nKey: 'video.player.error706',
                type: 'option'
            }
        }
    })
    //在Function的原型上自定义myBind()方法
    Function.prototype.myBind = function myBind(context) {
        //获取要操作的函数
        var _this = this
        //获取实参（context除外）
        var args = Array.prototype.slice.call(arguments, 1)

        //判断当前浏览器是否兼容bind()方法
        if ('bind' in Function.prototype) {
            //如果浏览器兼容bind()方法，则使用bind()方法，并返回bind()方法执行后的结果
            return _this.bind(context, args)
        }
        //如果不兼容bind()方法，则返回一个匿名函数
        return function () {
            _this.apply(context, args)
        }
    }

    if (!document.getElementsByClassName) {
        document.getElementsByClassName = function (className, element) {
            var children = (element || document).getElementsByTagName('*')
            var elements = new Array()
            for (var i = 0; i < children.length; i++) {
                var child = children[i]
                var classNames = child.className.split(' ')
                for (var j = 0; j < classNames.length; j++) {
                    if (classNames[j] == className) {
                        elements.push(child)
                        break
                    }
                }
            }
            return elements
        }
    }

    /**
     * 匹配插件上的版本信息
     * @param {*} param
     * @param {*} type
     * @returns Object 版本匹配信息
     */
    function getVersionInfo() {
        if (window.dhPlayerControl.pkgDHPlayerVerion.includes(window.dhPlayerControl.DHPlayerVersion)) {
            return {
                isEqual: true,
                code: 0,
                i18nKey: "video.player.plugin.version.ok", 
                message: '创建成功'
            }
        }
        if (window.dhPlayerControl.pkgDHPlayerVerion[0] > window.dhPlayerControl.DHPlayerVersion) {
            return {
                isEqual: false,
                code: -1,
                i18nKey: 'video.player.plugin.version.low.redownload',
                message: '当前电脑上的插件版本过低,建议升级插件!'
            }
        }
        if (window.dhPlayerControl.pkgDHPlayerVerion[window.dhPlayerControl.pkgDHPlayerVerion.length - 1] < window.dhPlayerControl.DHPlayerVersion) {
            return {
                isEqual: true,
                code: 1,
                i18nKey: 'video.player.plugin.version.high.redownload',
                message: '创建成功'
            }
        }
    }

    /**
     * 内部方法 封装请求参数
     * @param {*} param
     * @param {*} type 接口类型
     * @returns Object 请求参数
     */
    function getAjaxParam(param, type) {
        // 处理对讲参数
        let processTalkParam = function (param) {
            if (param.channelId) {
                let tempArr = param.channelId.split('$1$0$')
                !tempArr && (tempArr = param.channelId.split('$'))
                return tempArr
            } else {
                return [param.deviceCode, param.channelSeq]
            }
        }
        let obj = {
            // 实时预览参数
            real: {
                data: {
                    channelId: param.channelId || '',
                    streamType: Number(param.streamType) || 1, // 默认主码流
                    dataType: Number(param.dataType) || 1 // 默认是视频
                }
            },
            // 对讲参数
            talk: {
                data: {
                    audioBit: 16,
                    sampleRate: 8000,
                    audioType: 2,
                    talkType: getTalkType(param.deviceType),
                    deviceCode: processTalkParam(param)[0],
                    channelSeq: processTalkParam(param)[1],
                    enableGBParamAutoAdapt: 1,
                    // urlType: 1,
                    // audioTypeList: [1, 2, 8]
                }
            },
            // 停止对讲参数
            stopTalk: {
                data: {
                    deviceCode: processTalkParam(param)[0],
                    channelSeq: processTalkParam(param)[1],
                    talkType: getTalkType(param.deviceType),
                    session: param.session
                }
            },
            // 通过时间录像回放参数
            playbackByTime: {
                clientType: "WINPC",
                clientMac: "30:9c:23:79:40:08",
                clientPushId: "",
                project: "PSDK",
                method: "SS.Playback.StartPlaybackByTime",
                data: {
                    nvrId: "",
                    optional: "/admin/API/SS/Playback/StartPlaybackByTime",
                    recordType: "0",	// 录像类型：1=一般录像，2=报警录像
                    recordSource: param.recordSource, // 录像来源：1=全部，2=设备，3=中心 4-统一云
                    streamType: param.streamType || 0, // 码流类型： 0=所有码流，1=主码流，2=辅码流
                    channelId: param.channelId,
                    startTime: param.bBack === 0 ? (param.currentPlayTime || param.startTime) : param.startTime,
                    endTime: param.bBack === 0 ? param.endTime : (param.currentPlayTime || param.endTime)
                }
            },
            // 通过文件录像回放参数
            playbackByFile: {
                clientType: "WINPC",
                clientMac: "30:9c:23:79:40:08",
                clientPushId: "",
                project: "PSDK",
                method: "SS.Playback.StartPlaybackByFile",
                data: {
                    ssId: "1001",
                    optional: "/evo-apigw/admin/API/SS/Playback/StartPlaybackByFile",
                    startTime: param.bBack === 0 ? param.currentPlayTime : param.playStartTime,
                    endTime: param.bBack === 0 ? param.playEndTime : param.currentPlayTime,
                    fileName: "{fe69f729-9d4b-42d4-b6a0-56189aaa4e1e}",
                    diskId: "1540852944-1540853395",
                    nvrId: "",
                    recordSource: param.recordSource,
                    channelId: param.channelId,
                    playbackMode: "0",
                    streamId: "5875"
                }
            },
            // 查询录像参数
            queryRecord: {
                clientType: "WINPC",
                clientMac: "30:9c:23:79:40:08",
                clientPushId: "",
                project: "PSDK",
                method: "SS.Record.QueryRecords",
                "data": {
                    cardNo: "",
                    optional: "/admin/API/SS/Record/QueryRecords",
                    diskPath: "",
                    startIndex: "",
                    streamType: param.streamType || 0, // 码流类型：0= 所有码流, 1=主码流, 2=辅码流
                    recordType: "0", // 录像类型：0=全部，1=手动录像，2=报警录像，3=动态监测，4=视频丢失，5=视频遮挡，6=定时录像，7=全天候录像，8=文件录像转换
                    recordSource: param.recordSource, // 录像来源：1=全部，2=设备，3=中心 4-统一云
                    endIndex: "",
                    startTime: param.startTime,
                    endTime: param.endTime,
                    channelId: param.channelId,
                }
            }
        }
        // 对应的窗口号
        obj[type].snum = param.snum
        return JSON.parse(JSON.stringify(obj[type]))
    }

    /**
     * 获取对讲类型
     * @param { Number } deviceType
     * @returns talkType 对讲类型  1-设备对讲 2-通道对讲
     * @desc 如果是EVS{1}, NVS{3}，NVR{6}, DVR{10}, smartNVR{14}[已弃用]，IVSS{43},需要发起通道对讲。
     */
    function getTalkType(deviceType) {
        let channelTalk = [1, 3, 6, 10, 14, 43]
        if (channelTalk.includes(Number(deviceType))) {
            return 2
        }
        return 1
    }


    /**
     * 内部方法
     * @desc 判断当前dom是否被 visibility: hidden 或者 display: none
     * @param {*} data
     * @returns { Boolean } visible
     */
    function isDomVisible(el) {
        // var loopable = true,
        // visible = getComputedStyle(el).display != 'none' && getComputedStyle(el).visibility != 'hidden';
        // 代码保留，不删除，递归访问
        // while(loopable && visible) {
        //     el = el.parentNode;
        //     if(el && el != document.body) {; 
        //         visible = getComputedStyle(el).display != 'none' && getComputedStyle(el).visibility != 'hidden';
        //     }else {
        //         loopable = false;
        //     }
        // }
        return el && (window.getComputedStyle(el).display != 'none' || window.getComputedStyle(el).visibility != 'hidden') || false;
    }

    /**
     * 内部方法
     * @desc rtsp路径拼接token
     * @param {Object} 接口返回的rtsp对象
     */
    function dealUrl(data) {
        let path = data.url
        let compareUrl = ""
        if (path.includes('|')) {
            path = path.split("|").map(item => {
                // 视频子系统兼容
                if (item.includes(window.location.hostname)) {
                    compareUrl = item + (data.token ? '?token=' + data.token : '')
                    return null
                }
                return item + (data.token ? '?token=' + data.token : '')
            }).filter(item => item).join('|')
            path = compareUrl ? (compareUrl + '|') + path : path
            // 兼容视频子系统
        } else {
            path = path + (data.token ? '?token=' + data.token : '')
        }
        return path
    }

    /**
     * 内部方法
     * @desc 处理当前浏览器的缩放比例
     */
    function getWindowSize() {
        var width = window.dhPlayerControl.isPIframe ? this.setting.topInnerWidth : window.top.innerWidth,
            height = window.dhPlayerControl.isPIframe ? this.setting.topInnerHeight : window.top.innerHeight
        return {
            width: width,
            height: height
        }
    }

    /**
     * 内部方法
     * @desc 获取当前浏览器最左侧距离主屏的位置
     */
    function getScreenX() {
        // 造个假数据,模拟客户端返回
        let screenInfo = window.osRatio || [1, 1] // [主屏的缩放比例， 副屏的缩放比例]
        let defaultScreenX = window.screenX
        let availX = window.screen.availLeft
        let x = 0
        if (availX <= 0) {
            // 不需要计算主屏的位置
            x = defaultScreenX >= -2 && defaultScreenX <= 9 ? 0 : defaultScreenX
            // availX === 0 ? 0 : 1  为 0 表示在主屏上面， 小于 0 表示在副屏上，所以获取的分辨率不同。
            x = x * screenInfo[availX === 0 ? 0 : 1]
        } else {
            // 需要计算主屏的位置
            let sideX = defaultScreenX - availX
            sideX = sideX >= -2 && sideX <= 9 ? 0 : sideX
            // 计算主屏和副屏的真实距离后相加，即为x
            x = availX * screenInfo[0] + sideX * screenInfo[1]
        }
        return x
    }
    /**
     * 内部方法
     * @desc 获取页面缩放比例
     */
    function detectZoom() {
        var ratio = 0,
            screen = window.screen,
            ua = navigator.userAgent.toLowerCase()
        if (window.devicePixelRatio !== undefined) {
            ratio = window.devicePixelRatio
        } else if (~ua.indexOf('msie')) {
            if (screen.deviceXDPI && screen.logicalXDPI) {
                ratio = screen.deviceXDPI / screen.logicalXDPI
            }
        } else if (window.outerWidth !== undefined && window.innerWidth !== undefined) {
            ratio = window.outerWidth / window.innerWidth
        }

        if (ratio) {
            ratio = Math.round(ratio * 100)
        }
        return ratio
    }


    function socketOpen(curPortIndex = 0) {
        if (typeof WebSocket === 'undefined') {
            this.setting.createError && this.setting.createError({
                code: 1005,
                data: null,
                success: false,
                i18nKey: 'browser.not.support.socket',
                message: "您的浏览器不支持socket!"
            })
            return
        }
        window.dhPlayerControl.videoWS = new WebSocket(`ws:127.0.0.1:${[window.dhPlayerControl.connectPort[curPortIndex]]}`)
        window.dhPlayerControl.videoWS.onopen = function () {
            window.dhPlayerControl.manualCloseWS = false
            window.dhPlayerControl.windowState = 'wsSuccess' // websocket连接成功
            window.dhPlayerControl.curPortIndex = curPortIndex
            heartbeat.call(this)
            let _isSupport = isSupport()
            for (let key in window.dhPlayerControl.videoList) {
                let currentThis = window.dhPlayerControl.videoList[key]
                currentThis.send({
                    method: 'common.version',
                    info: {},
                })
                if (_isSupport.success) {
                    if(currentThis.setting.usePluginLogin) {
                        currentThis.loginServer()
                        window.dhPlayerControl.loginFlag = "LOGIN_PENDING"
                        let p = new Promise((resolve, reject) => {
                            let interval = setInterval(() => {
                                if(window.dhPlayerControl.loginFlag !== "LOGIN_PENDING") {
                                    clearInterval(interval)
                                    resolve()
                                }
                                
                            }, 300)
                        })
                        p.then(() => {
                            currentThis.create()
                            window.isResetConnect = currentThis.setting.isResetConnect
                        })
                    } else {
                        currentThis.create()
                        window.isResetConnect = currentThis.setting.isResetConnect
                    }
                    
                } else {
                    currentThis.setting.createError(_isSupport)
                }
            }
        }
        window.dhPlayerControl.videoWS.onmessage = socketMessage
        window.dhPlayerControl.videoWS.onclose = () => {
            if(window.dhPlayerControl.windowState === 'wsPending' || window.dhPlayerControl.windowState === 'noPlugin') {
                if(curPortIndex < window.dhPlayerControl.connectPort.length - 1) {
                    window.dhPlayerControl.timer = setTimeout(() => {
                        window.dhPlayerControl.videoWS = null
                        socketOpen(curPortIndex + 1)
                        clearTimeout(window.dhPlayerControl.timer)
                    }, 300)
                    return
                }
                // 未成功过，轮询端口
                window.dhPlayerControl.windowState = 'noPlugin' // websocket连接失败
                for (var key in window.dhPlayerControl.videoList) {
                    var currentThis = window.dhPlayerControl.videoList[key]
                    currentThis.setting.createError && currentThis.setting.createError({
                        code: 1001,
                        data: null,
                        i18nKey: 'video.player.plugin.not.installed',
                        message: '插件未安装',
                        success: false
                    })
                }
            } else {
                // 表示成功过，不需要再轮训判断端口了
                window.dhPlayerControl.windowState = 'wsError' // websocket连接失败
                if (window.isResetConnect && !window.dhPlayerControl.manualCloseWS) {
                    console.log("----------无法与插件建立连接-------");
                    for (var key in window.dhPlayerControl.videoList) {
                        var currentThis = window.dhPlayerControl.videoList[key]
                        currentThis.setting.createError && currentThis.setting.createError({
                            code: 1003,
                            data: null,
                            i18nKey: 'websocket.reload',
                            message: '无法与播放器建立连接，正在尝试重连...',
                            success: false
                        })
                    }
                    setTimeout(() => {
                        window.dhPlayerControl.videoWS = null
                        socketOpen(window.dhPlayerControl.curPortIndex)
                    }, 3000)
                } else {
                    for (var key in window.dhPlayerControl.videoList) {
                        var currentThis = window.dhPlayerControl.videoList[key]
                        currentThis.setting.createError && currentThis.setting.createError({
                            code: 1003,
                            data: null,
                            i18nKey: 'websocket.disconnect',
                            message: '无法与播放器建立连接',
                            success: false
                        })
                    }
                }
            }
        }
    }

    /**
     * 内部方法
     * @desc 插件心跳，保活
     */
    function heartbeat() {
        var that = this
        clearInterval(window.wsHeart)
        window.wsHeart = setInterval(function () {
            that.send(JSON.stringify({
                method: 'common.heartbeat',
                info: {},
                id: window.dhPlayerControl.wsConnectCount++
            }))
        }, 10 * 1000)
    }

    function dataURLtoBlob(dataurl) {
        var mime = 'image/jpeg',
            bstr = atob(dataurl),
            n = bstr.length,
            u8arr = new Uint8Array(n)
        while (n--) {
            u8arr[n] = bstr.charCodeAt(n)
        }
        return new Blob([u8arr], {
            type: mime
        })
    }
    function downloadFileByBase64(base64, name) {
        var myBlob = dataURLtoBlob(base64)
        var myUrl = URL.createObjectURL(myBlob)
        return myUrl
    }

    /**
     * 前端与插件链接成功后，接收插件返回的信息
     * @param {*} evt
     * @returns
     */
    function socketMessage(evt) {
        if (evt.data == null || evt.data == '') {
            return
        }
        window.dhPlayerControl.wsSession = data && data.session || window.dhPlayerControl.wsSession
        var data = JSON.parse(evt.data)
        if(data.method && ![
            'window.osRatio','video.window.clicked',
            'video.window.dbclicked',
            'video.division.change',
            'video.customDivision.change',
            'video.downloadFileSize',
            'video.download.mp4.result',
            'video.downloadByTime'].includes(data.method)) {
            localStorage.printLog && console.log('client->web: ', data.method, data.info);
        }
        // 登录校验
        if(data.method === 'window.loginServer') {
            switch(data.info.code) {
                case 0:
                case 3:
                    window.dhPlayerControl.loginFlag = "LOGIN_PENDING"
                    break;
                case 1:
                case 2:
                    window.dhPlayerControl.loginFlag = "LOGIN_SUCCESS"
                    break;
                case 4:
                    window.dhPlayerControl.loginFlag = "LOGIN_ERROR"
                default:
                    break;
            }
        }
        if(data.method === 'window.loginServer.notify') {
            if (data.info.result === 0) {
                window.dhPlayerControl.loginFlag = "LOGIN_SUCCESS"
            } else {
                window.dhPlayerControl.loginFlag = "LOGIN_ERROR"
            }
        }
        // 赋值版本号信息
        if (data && data.data && data.data.ver) {
            window.dhPlayerControl.DHPlayerVersion = Number(data.data.ver)
        }
        // 获取屏幕分辨率
        if (data.method === 'window.osRatio') {
            window.osRatio = data.info.dpi
        }
        // 保证 hwnd 是 number 类型
        if (data.info && typeof data.info.hwnd === 'number') {
            var videoInfo = window.dhPlayerControl.videoList[window.dhPlayerControl.hwndList[data.info.hwnd]]
            var hwndInfo = videoInfo.setting
            if (typeof data.info.snum === 'number') {
                var channelInfo = hwndInfo.channelList.filter(item => item.snum === data.info.snum)[0]
            }
            switch (data.method) {
                case 'window.message':
                    hwndInfo.dhPlayerMessage && hwndInfo.dhPlayerMessage(channelInfo, window.dhPlayerControl.dhMessage[data.info.eventCode])
                    break
                case 'video.change.substream':
                    if (channelInfo.byUrl) {
                        return hwndInfo.dhPlayerMessage && hwndInfo.dhPlayerMessage(channelInfo, {...window.dhPlayerControl.dhMessage[706], streamType: Number(data.info.substream)})
                    }
                    channelInfo.streamType = Number(data.info.substream)
                    videoInfo.startReal([channelInfo], { isSubStream: true })
                    break
                case 'video.notifyrealmonitor':
                    // 表示成功
                    if (Number(data.info.result) === 0) {
                        hwndInfo.realSuccess && hwndInfo.realSuccess(channelInfo, hwndInfo.channelList)
                    }
                    // 拉流失败
                    if (Number(data.info.result) === 10704) {
                        hwndInfo.realError && hwndInfo.realError(channelInfo, { code: 10704, message: '打开视频失败', i18nKey: 'video.player.play.fail'})
                    }
                    // 拉流超时
                    if (Number(data.info.result) === -405) {
                        hwndInfo.realError && hwndInfo.realError(channelInfo, { code: 10705, message: '打开视频超时', i18nKey: 'video.player.play.timeout'})
                    }
                    videoInfo.setWindowDragEnable()
                    break
                // 实时预览播放过程中的断线重连
                case 'video.reopenvideo':
                    if (channelInfo.byUrl) {
                        return hwndInfo.realError(channelInfo, { code: 204, message: '流异常断开，请重新连接~', i18nKey: 'video.player.stream.disconnect.reload'});
                    }
                    data.info.count++
                    videoInfo.startReal([channelInfo], { isReOpen: true, count: data.info.count++ })
                    break;
                case 'video.notifyplayback':
                    // 表示成功
                    if (Number(data.info.result) === 0) {
                        hwndInfo.playbackSuccess && hwndInfo.playbackSuccess(channelInfo, hwndInfo.channelList)
                    }
                    videoInfo.setWindowDragEnable()
                    break
                case 'web.seekRecord':
                    hwndInfo.channelList.forEach((item, index) => {
                        if (item.snum === data.info.snum) {
                            // 非集成状态下，抛出回调
                            if (item.byUrl) {
                                hwndInfo.switchStartTime && hwndInfo.switchStartTime({
                                    startTime: data.info.seekTime,
                                    snum: data.info.snum
                                })
                                return
                            }
                            if (data.info.seekTime < item.startTime) {
                                hwndInfo.playbackError && hwndInfo.playbackError(channelInfo, {
                                    code: 201,
                                    i18nKey: 'play.record.error.201.tip1',
                                    message: '当前时间不得小于开始时间'
                                })
                                return
                            }
                            if (data.info.seekTime > item.endTime) {
                                hwndInfo.playbackError && hwndInfo.playbackError(channelInfo, {
                                    code: 201,
                                    i18nKey: 'play.record.error.201.tip2',
                                    message: '当前时间不得大于结束时间'
                                })
                                return
                            }
                            let currentIndex = item.records && item.records.findIndex(r => {
                                return Number(r.startTime) <= data.info.seekTime && Number(r.endTime) > data.info.seekTime
                            })
                            // 如果
                            if(currentIndex >= 0) {
                                item.currentIndex = currentIndex
                                console.log(`当前拖拽到第${item.currentIndex}录像`);
                                item.currentPlayTime = data.info.seekTime
                                item.bBack = Number(data.info.bBack)
                                videoInfo.closeVideo(data.info.snum).then(res => {
                                    videoInfo.startPlayback(item, {
                                        isConnect: true,
                                        scaleSteps: Number(data.info.scaleSteps),
                                    })
                                })
                            } else {
                                console.log("当前位置无录像，不能拖拽")
                            }
                        }
                    })
                    break
                case 'web.replay':
                    hwndInfo.channelList.forEach((item, index) => {
                        if (item.snum === data.info.snum) {
                            // 非集成状态下
                            if (item.byUrl) {
                                hwndInfo.replay && hwndInfo.replay(data.info.snum, data.info.seekTime)
                                return
                            }
                            item.bBack = Number(data.info.bBack)
                            if (!item.bBack) {
                                if (item.currentIndex === item.records.length - 1) {
                                    hwndInfo.playbackFinish && hwndInfo.playbackFinish({
                                        snum: data.info.snum,
                                        channelId: data.info.channelId,
                                        code: 201,
                                        i18nKey: 'video.player.playback.complete',
                                        message: '录像已全部播放完成'
                                    })
                                    // videoInfo.closeVideo(data.info.snum)
                                    // hwndInfo.channelList.splice(index, 1)
                                    return
                                }
                                item.currentIndex++
                                item.currentPlayTime = item.records[item.currentIndex].startTime
                            } else {
                                if (item.currentIndex === 0) {
                                    hwndInfo.playbackFinish && hwndInfo.playbackFinish({
                                        snum: data.info.snum,
                                        channelId: data.info.channelId,
                                        code: 201,
                                        i18nKey: "video.player.playback.complete",
                                        message: '录像已全部播放完成'
                                    })
                                    // videoInfo.closeVideo(data.info.snum)
                                    // hwndInfo.channelList.splice(index, 1)
                                    return
                                }
                                item.currentIndex--
                                item.currentPlayTime = item.records[item.currentIndex].endTime
                            }
                            if (item.recordSource === 2) {
                                // 设备录像要关闭一次视频
                                videoInfo.closeVideo(data.info.snum).then(() => {
                                    videoInfo.startPlayback(item, {
                                        isConnect: true,
                                        scaleSteps: Number(data.info.scaleSteps)
                                    })
                                })
                            } else {
                                // 中心录像不需要
                                videoInfo.startPlayback(item, {
                                    isConnect: true,
                                    bContinue: true,
                                    scaleSteps: Number(data.info.scaleSteps)
                                })
                            }
                        }
                    })
                    break
                case 'video.close':
                    let i = -1
                    hwndInfo.channelList.forEach((item, index) => {
                        // 获取到要被删除的数据 (插件内部关闭且存在窗口统一)
                        if (item.snum === data.info.snum) {
                            if (!item.closed) {
                                i = index
                            } else {
                                delete item.closed
                            }
                        }
                    })
                    if (i >= 0) {
                        hwndInfo.channelList.splice(i, 1)
                        // 删除掉窗口和channelId都匹配上的
                        hwndInfo.closeWindowSuccess && hwndInfo.closeWindowSuccess({
                            snum: data.info.snum,
                            channelList: hwndInfo.channelList
                        })
                    }
                    break
                case 'video.notifytalk':
                    // 判断是否有对讲
                    if (data.info.bOpen) {
                        let talkFlag = false
                        hwndInfo.channelList.forEach(item => {
                            if (item.byUrl) {
                                talkFlag = true
                                // 告诉前端传入url对讲回调
                                hwndInfo.notifyTalk && hwndInfo.notifyTalk({ channelId: data.info.channelId, snum: data.info.snum })
                                return
                            }
                            if (item.isTalk) {
                                talkFlag = true
                                hwndInfo.request.stopTalk && hwndInfo.request.stopTalk(getAjaxParam(item, 'stopTalk')).then(() => {
                                    delete item.isTalk
                                    videoInfo.startTalk(data.info.snum)
                                }).catch(err => {
                                    // 表示当前不存在
                                    if (err.code === 2051) {
                                        videoInfo.startTalk(data.info.snum)
                                    }
                                })
                            }
                        })
                        !talkFlag && videoInfo.startTalk(data.info.snum)
                    } else {
                        hwndInfo.channelList.forEach(item => {
                            if (item.snum === data.info.snum) {
                                hwndInfo.request.stopTalk && hwndInfo.request.stopTalk(getAjaxParam(item, 'stopTalk')).then(() => {
                                    console.log('关闭对讲')
                                })
                            }
                            videoInfo.closeTalk()
                        })
                    }
                    break
                case 'talk.close':
                    hwndInfo.snum = data.info.wndId
                    hwndInfo.closeTalkSuccess && hwndInfo.closeTalkSuccess(data.info.wndId)
                    break
                case 'web.captureCallBack':
                    var imageUrl = data.info.PicBuf ? downloadFileByBase64(data.info.PicBuf) : ''
                    hwndInfo.snapshotSuccess && hwndInfo.snapshotSuccess({
                        base64Url: data.info.PicBuf,
                        path: imageUrl
                    }, channelInfo)
                    break
                case 'window.LocalRecordFinish':
                    hwndInfo.videoDownloadSuccess && hwndInfo.videoDownloadSuccess(data.info.path, channelInfo)
                    break
                case 'video.window.clicked':
                    channelInfo = hwndInfo.channelList.filter(item => item.snum === data.info.wndIndex)[0]
                    hwndInfo.clickWindow && hwndInfo.clickWindow(data.info.wndIndex, channelInfo)
                    break
                case 'video.window.dbclicked':
                    channelInfo = hwndInfo.channelList.filter(item => item.snum === data.info.wndIndex)[0]
                    hwndInfo.dbClickWindow && hwndInfo.dbClickWindow(data.info.wndIndex, channelInfo)
                    break
                case 'video.division.change':
                    hwndInfo.division = data.info.division
                    hwndInfo.changeDivision && hwndInfo.changeDivision(data.info.division)
                    videoInfo.setWindowDragEnable()
                    break
                case 'video.customDivision.change':
                    hwndInfo.division = JSON.stringify(data.info)
                    hwndInfo.changeDivision && hwndInfo.changeDivision(JSON.stringify(data.info))
                    videoInfo.setWindowDragEnable()
                    break
                case 'video.downloadFileSize':
                    hwndInfo.downloadProgress && hwndInfo.downloadProgress(data.info)
                    break
                case 'video.download.mp4.result':
                    hwndInfo.downloadRecordSuccess && hwndInfo.downloadRecordSuccess(data.info)
                    break;
                case 'video.downloadByTime':
                    if (Number(data.info.errCode) === 0) {
                        hwndInfo.downloadRecordSuccess && hwndInfo.downloadRecordSuccess(data.info)
                    } else {
                        let errMessage = {
                            142: "rtsp断开连接",
                            135: "视频异常",
                            141: "SS服务异常"
                        }
                        hwndInfo.downloadRecordError && hwndInfo.downloadRecordError(data.info, {
                            code: Number(data.info.errCode),
                            mesage: errMessage[Number(data.info.errCode)]
                        })
                    }
                default:
                    break
            }
        }
        var onError = null
        var onSuccess = null
        if (window.dhPlayerControl.callBackList[data['id']]) {
            onError = window.dhPlayerControl.callBackList[data['id']].onError
            onSuccess = window.dhPlayerControl.callBackList[data['id']].onSuccess
        }
        if (data.code != 0) {
            if (onError && typeof onError === 'function') {
                onError(data)
                delete window.dhPlayerControl.callBackList[data['id']]
            }
            return
        }
        if (onSuccess && typeof onSuccess === 'function') {
            onSuccess(data)
            delete window.dhPlayerControl.callBackList[data['id']]
        }
    }
    //主动关闭socket
    function socketClose() {
        window.dhPlayerControl.manualCloseWS = true
        window.dhPlayerControl.videoWS && window.dhPlayerControl.videoWS.close()
        window.dhPlayerControl.videoWS = null
        window.wsHeart = clearInterval(window.wsHeart)
    }

    /**
     * @desc 获取操作系统
     */
    function getOsInfo() {
        var userAgent = window.navigator.userAgent.toLowerCase()
        var version = ''
        if (userAgent.indexOf('win') > -1) {
            if (userAgent.indexOf('windows nt 5.0') > -1 || userAgent.indexOf('Windows 2000') > -1) {
                version = 'Windows 2000'
            } else if (userAgent.indexOf('windows nt 5.1') > -1 || userAgent.indexOf('Windows XP') > -1) {
                version = 'Windows XP'
            } else if (userAgent.indexOf('windows nt 5.2') > -1 || userAgent.indexOf('Windows 2003') > -1) {
                version = 'Windows 2003'
            } else if (userAgent.indexOf('windows nt 6.0') > -1 || userAgent.indexOf('Windows Vista') > -1) {
                version = 'Windows Vista'
            } else if (userAgent.indexOf('windows nt 6.1') > -1 || userAgent.indexOf('windows 7') > -1) {
                version = 'Windows 7'
            } else if (userAgent.indexOf('windows nt 6.2') > -1 || userAgent.indexOf('windows 8') > -1) {
                version = 'Windows 8'
            } else if (userAgent.indexOf('windows nt 6.3') > -1) {
                version = 'Windows 8.1'
            } else if (userAgent.indexOf('windows nt 6.4') > -1 || userAgent.indexOf('windows nt 10') > -1) {
                version = 'Windows 10'
            } else {
                version = 'Unknown'
            }
        } else if (userAgent.indexOf('iphone') > -1) {
            version = 'Iphone'
        } else if (userAgent.indexOf('mac') > -1) {
            version = 'Mac'
        } else if (
            userAgent.indexOf('x11') > -1 ||
            userAgent.indexOf('unix') > -1 ||
            userAgent.indexOf('sunname') > -1 ||
            userAgent.indexOf('bsd') > -1
        ) {
            version = 'Unix'
        } else if (userAgent.indexOf('linux') > -1) {
            if (userAgent.indexOf('android') > -1) {
                version = 'Android'
            } else {
                version = 'Linux'
            }
        } else {
            version = 'Unknown'
        }
        return version
    }

    /**
     * @desc 获取浏览器和对应浏览器版本
     */
    function getBroswerVersion() {
        // 浏览器判断和版本号读取
        var Sys = {}
        var userAgent = navigator.userAgent.toLowerCase()
        var s;
        (s = userAgent.match(/edg\/([\d.]+)/)) ?
            (Sys.edge = s[1]) :
            (s = userAgent.match(/rv:([\d.]+)\) like gecko/)) ?
                (Sys.ie = s[1]) :
                (s = userAgent.match(/msie ([\d.]+)/)) ?
                    (Sys.ie = s[1]) :
                    (s = userAgent.match(/firefox\/([\d.]+)/)) ?
                        (Sys.firefox = s[1]) :
                        (s = userAgent.match(/chrome\/([\d.]+)/)) ?
                            (Sys.chrome = s[1]) :
                            (s = userAgent.match(/opera.([\d.]+)/)) ?
                                (Sys.opera = s[1]) :
                                (s = userAgent.match(/version\/([\d.]+).*safari/)) ?
                                    (Sys.safari = s[1]) :
                                    0

        if (Sys.edge)
            return {
                broswer: 'Edg',
                version: Sys.edge
            }
        if (Sys.ie)
            return {
                broswer: 'IE',
                version: Sys.ie
            }
        if (Sys.firefox)
            return {
                broswer: 'Firefox',
                version: Sys.firefox
            }
        if (Sys.chrome)
            return {
                broswer: 'Chrome',
                version: Sys.chrome
            }
        if (Sys.opera)
            return {
                broswer: 'Opera',
                version: Sys.opera
            }
        if (Sys.safari)
            return {
                broswer: 'Safari',
                version: Sys.safari
            }

        return {
            broswer: '',
            version: '0'
        }
    }


    function broswerInfo() {
        var _version = getBroswerVersion()
        if (_version.broswer === 'IE') {
            return 0
        } else if (_version.broswer === 'Chrome' || _version.broswer === 'Edg') {
            return 1
        } else if (_version.broswer === 'Firefox') {
            return 2
        } else {
            return -1
        }
    }

    /**
     * 内部方法
     * @desc 判断当前操作系统和浏览器版本类型是否支持DHPlayer
     */
    function isSupport() {
        let supportedOS = ['Windows 7', 'Windows 10', 'Windows 8', 'Windows 8.1', 'Unix', 'Linux']
        let supportedBroswer = ['Chrome', 'Firefox', 'Edg']
        let osVersion = getOsInfo()
        let { broswer, version } = getBroswerVersion()
        console.log("当前识别的操作系统版本为： ", osVersion, "浏览器为: ", broswer, version + "版本")
        if (!supportedOS.includes(osVersion)) {
            return {
                code: 1002,
                success: false,
                i18nKey: 'window.system.not.support',
                message: '电脑系统不支持！仅支持win7, win8, win8.1, win10, Unix, Linux 系统'
            }
        }
        if (!supportedBroswer.includes(broswer)) {
            return {
                code: 1002,
                success: false,
                i18nKey: 'browser.not.support',
                message: '当前浏览器不支持！ 仅支持谷歌，火狐，edge浏览器'
            }
        }
        if (Number(version.split('.')[0]) < 76 || osVersion === 'unix') {
            return {
                code: 1002,
                success: false,
                i18nKey: 'browser.version.low',
                message: '当前的浏览器版本不支持！请使用较高版本的浏览器！'
            }
        }
        return {
            code: 1000,
            success: true
        }
    }

    // 获取iframe的位置
    function getIframeRect(name) {
        var outLeft = 0
        var outTop = 0
        var pOutContent
        if (window.dhPlayerControl.isPIframe) {
            pOutContent = this.setting.pIframeRect
            outLeft = pOutContent.left || 0
            outTop = pOutContent.top || 0
        } else {
            // 表示顶层window
            if(window.parent === window) {
                return {
                    outLeft: 0,
                    outTop: 0
                }
            }
            var iframes = window.parent.document.getElementsByTagName('iframe')
            var pIframe = null
            var dom = ''
            let getpIframe = (index) => {
                if (!iframes[index]) return
                try {
                    if (name) {
                        dom = iframes[index].contentWindow.document.getElementsByClassName(name)[0]
                    } else {
                        dom = iframes[index].contentWindow.document.getElementById(this.setting.videoId)
                    }
                    if (dom) {
                        pIframe = iframes[index]
                    } else {
                        getpIframe(index + 1)
                    }
                } catch (err) {
                    getpIframe(index + 1)
                }
            }
            iframes.length && getpIframe(0)
            if (pIframe) {
                pOutContent = pIframe.getBoundingClientRect()
                outLeft = pOutContent.left
                outTop = pOutContent.top
            }
        }
        return {
            outLeft,
            outTop
        }
    }

    // 获取遮挡的位置数据
    function computedRect(className, isIframe) {
        let doms = null;
        let { outLeft, outTop } = getIframeRect.call(this)
        if (isIframe) {
            outLeft = 0
            outTop = 0
        }
        // 处理dom的位置数据
        let processDoms = (doms) => {
            let rectArr = []
            doms.length && doms.forEach(item => {
                let rect = item && item.getBoundingClientRect() || null
                if (rect && (rect.width || rect.height)) {
                    rectArr = [...rectArr, rect.left + (this.setting.outContent.left || outLeft), rect.top + (this.setting.outContent.top || outTop), rect.width, rect.height]
                }
            })
            return rectArr
        }
        if (isIframe) {
            doms = window.top.document.querySelectorAll(`.${className}`)
        } else {
            doms = window.document.querySelectorAll(`.${className}`)
        }
        return processDoms(doms)
    }

    // 获取位置
    function getRect(name) {
        var el = ''
        var videoId = this.setting.videoId
        if (name) {
            el = document.getElementsByClassName(name)[0]
        } else {
            el = document.getElementById(videoId)
        }
        if (!el) {
            return {}
        }
        var rect = el.getBoundingClientRect()
        var { outTop, outLeft } = getIframeRect.call(this, name)
        var left = rect.left + (this.setting.outContent.left || outLeft)
        var top = rect.top + (this.setting.outContent.top || outTop)
        var right = left + rect.width
        var bottom = top + rect.height
        return {
            left,
            top,
            right,
            bottom,
            width: rect.width,
            height: rect.height
        }
    }
    // 请求录像文件信息
    function queryRecord(param, type = 'queryRecord') {
        return new Promise((resolve, reject) => {
            // 查询录像
            if (param.records && param.records.length) {
                resolve(param)
            }
            if (!this.setting.request[type]) {
                reject({
                    code: 207,
                    i18nKey: 'video.player.please.input.recordings.interface',
                    message: '请通过 request 属性传入查询录像接口'
                })
                return
            }
            this.setting.request[type](getAjaxParam(param, type)).then(res => {
                if (!param.records || param.records === []) {
                    if (!res.records || !res.records.length) {
                        reject({
                            code: 201,
                            channelInfo: param,
                            i18nKey: 'video.player.selected.channel.search.empty',
                            message: `通道 ${param.channelName || param.name || '未知'} 未查询到录像文件`
                        })
                        return
                    }
                    param.records = res.records.sort((a, b) => a.startTime - b.startTime)
                    param.currentIndex = 0
                    this.setting.channelList[this.setting.channelList.findIndex(item => item.channelId === param.channelId && item.snum === param.snum)] = param
                }
                resolve(res)
            }).catch(err => {
                reject(err)
            })
        })
    }
    // 处理获取过来的rtsp流
    function processRtsp(data, param) {
        // 内外网环境会有多个rtspUrl
        data.records = param.records;
        data.rtspUrl = dealUrl(data)
        return data
    }

    /**
     * 根据文件获取流
     * @param {*} param
     */
    function getPlayBackRtspByFile(param, isConnect, type = 'playbackByFile') {
        let that = this
        return new Promise((resolve, reject) => {
            let byFileParam = getAjaxParam(param, type)
            queryRecord.call(this, param)
                .then((res) => {
                    if (!that.setting.request[type]) {
                        reject({
                            code: 207,
                            i18nKey: 'video.player.please.afferent.interface.playbackbyfile',
                            message: '请通过 request 属性传入 "根据时间查询录像" 接口'
                        })
                        return
                    }
                    let records = res.records[param.currentIndex]
                    // let rec = records[0]
                    param.playStartTime = records.startTime
                    param.playEndTime = records.endTime
                    param.currentPlayTime = isConnect ? param.currentPlayTime : param.playStartTime
                    let sTime = param.bBack === 0 ? String(param.currentPlayTime) : String(param.playStartTime)
                    let eTime = param.bBack === 0 ? String(param.playEndTime) : String(param.currentPlayTime)
                    byFileParam.data = {
                        ssId: records.ssId,
                        optional: "/evo-apigw/admin/API/SS/Playback/StartPlaybackByFile",
                        startTime: sTime,
                        endTime: eTime,
                        fileName: records.recordName,
                        diskId: `${sTime}-${eTime}`,
                        nvrId: "",
                        recordSource: param.recordSource,
                        channelId: param.channelId,
                        playbackMode: "0",
                        streamId: records.streamId
                    }
                    that.setting.request[type](byFileParam).then(res => {
                        resolve(processRtsp(res, param))
                    }).catch(err => {
                        reject(err)
                    })
                })
                .catch(err => {
                    reject(err)
                })
        })
    }

    /**
     * 根据时间获取流
     * @param {*} option
     * @returns
     */
    function getPlayBackRtspByTime(param, type = "playbackByTime") {
        // 设备录像也需要去查询当日有录像的时间段
        return new Promise((resolve, reject) => {
            queryRecord.call(this, param)
                .then(() => {
                    if (!this.setting.request[type]) {
                        reject({
                            code: 207,
                            i18nKey: 'video.player.please.afferent.interface.playbackbytime',
                            message: '请通过 request 属性传入 "通过时间播放录像" 接口'
                        })
                        return
                    }
                    this.setting.request[type](getAjaxParam(param, type)).then(res => {
                        resolve(processRtsp(res, param))
                    }).catch(err => {
                        reject(err)
                    })
                }).catch(err => {
                    reject(err)
                })
        })
    }
    var VideoPlayer = function (option) {
        if (!option) {
            throw new Error('请传入配置参数')
        }
        if (this instanceof VideoPlayer) {
            var _setting = {
                isResetConnect: true, // websocket连接断开时，是否自动重新连接， true表示是， false表示否
                // isIE: !!window.ActiveXObject || 'ActiveXObject' in window, //判断是否为IE
                videoId: 'DHVideoPlayer',
                windowType: 0, // 0-实时预览，3-录像回放，7-录像回放(支持倒放)
                outContent: {
                    left: 0,
                    right: 0,
                    width: 0,
                    height: 0
                },
                show: true, //当前窗口显示状态，隐藏：false，显示：true
                option_id: {},
                refreshTimer: null,
                browserType: 1,
                version: 0,
                stopRefresh: false, //停止一直刷新
                showBar: true, //是否显示下方控制栏。 true: 显示， false：隐藏
                hwnd: '', //窗口句柄
                division: 1, //子窗口数
                pIframeShieldData: [], // 跨域iframe下的遮挡信息
                documentTitle: "", // iframe模式下，需要获取到顶层的top，防止位置聚焦时发生偏移
                topInnerWidth: 0, // iframe模式下的顶层宽度
                topInnerHeight: 0, // iframe模式下的顶层高度
                parentIframeShieldRect: [], // iframe模式下遮罩的数据信息
                pIframeRect: [], // iframe模式下 iframe的数据信息
                topMozInnerScreenX: 0, // iframe模式下 火狐需要的数据信息
                topMozInnerScreenY: 0, // iframe模式下 火狐需要的数据信息
                oldPosition: "", // 存储循环数据中的上次位置数据
                oldShield: "", // 存储循环数据中的上次遮挡数据
                request: {}, // 存储请求
                channelList: [], // 存储当前播放器正在播放的视频
                draggable: false, // 是否支持拖拽，默认不支持
                showBorder: true, // 是否显示播放器边框
                visible: true, // 控制播放器的显示和隐藏
                domVisible: true, // 当前挂载的 dom 元素是否 true-显示/ false-隐藏, 默认true
                usePluginLogin: false, // 是否插件内部鉴权
                pluginLoginInfo: { // 插件登录信息
                    host: '',
                    port: '' || '443',
                    username: '',
                    password: '',
                }
            }
            this.setting = Object.assign({}, _setting, option)
            this.adjustCount = 0
            this.focus = false
            this.init()
        } else {
            return new VideoPlayer(option)
        }
    }
    VideoPlayer.fn = VideoPlayer.prototype = {
        // 浏览器关闭或者刷新
        onbeforeunload: function () {
            this.destroy(true).then(() => {
                socketClose.call(this)
            })
        },
        // 改变setting的参数值
        _update(param) {
            let paramType = (value, type) => Object.prototype.toString.call(value).includes(type)
            let { windowType, isResetConnect, request, division, visible, draggable, showBar, showBorder, shieldClass, coverShieldClass, parentIframeShieldClass, language } = param
            // 断线重连 (不对外开放，默认支持断线重连)
            paramType(isResetConnect, 'Boolean') && !(isResetConnect === this.setting.isResetConnect) && (this.setting.isResetConnect = isResetConnect)
            // 显隐播放器
            paramType(visible, 'Boolean') && !(visible === this.setting.visible) && (this.setting.visible = visible, visible ? this.show() : this.hide())
            // 显影控制栏
            paramType(showBar, 'Boolean') && !(showBar === this.setting.showBar) && this.showControlBar(showBar)
            // 播放器是否支持拖拽
            paramType(draggable, 'Boolean') && !(draggable === this.setting.draggable) && (this.setting.draggable = draggable, this.setWindowDragEnable())
            // 显隐选中的窗口边框
            paramType(showBorder, 'Boolean') && !(showBorder === this.setting.showBorder) && (this.setting.showBorder = showBorder, this.setWindowBorder())
            // 窗口分割
            !(division == this.setting.division) && this.changeDivision(division)
            // 接口转换
            paramType(request, 'Object') && (this.setting.request = { ...request })
            // 遮挡类的改变
            paramType(shieldClass, 'Array') && (this.setting.shieldClass = shieldClass)
            paramType(parentIframeShieldClass, 'Array') && (this.setting.parentIframeShieldClass = parentIframeShieldClass)
            paramType(coverShieldClass, 'Array') && (this.setting.coverShieldClass = coverShieldClass)
            // 重新创建
            windowType = Number(windowType);
            if ([0, 1, 2, 3, 7].includes(windowType) && windowType !== Number(this.setting.windowType) || language !== this.setting.language) {
                this.setting.language = language
                if (this.setting.socketTimer) {
                    clearTimeout(this.setting.socketTimer)
                };
                this.setting.socketTimer = setTimeout(() => {
                    this.setting.windowType = windowType
                    this.create()
                }, this.setting.usePluginLogin ? 1000 : 300)
            }
        },
        //发送消息
        send: function (option, callBack) {
            option.session = window.dhPlayerControl.wsSession
            option.id = window.dhPlayerControl.wsConnectCount++
            if (option.info) {
                option.info.browserType = this.setting.browserType
                if (option.method !== 'window.destroy') {
                    option.info.hwnd = option.method === 'window.create' ? undefined : this.setting.hwnd
                }
            }

            if (callBack && Object.keys(callBack).length) {
                window.dhPlayerControl.callBackList[option['id']] = callBack
            }
            if (!['window.change', 'window.shield', 'browserFocusBlur', 'window.show', 'window.loginServer', 'video.toolbar.showButton', 'window.enableDrag', 'video.division.change'].includes(option.method)) {
                localStorage.printLog && console.log('web->client: ', option.method, option.info)
            }
            window.dhPlayerControl.videoWS &&
                window.dhPlayerControl.videoWS.readyState == 1 &&
                window.dhPlayerControl.videoWS.send(JSON.stringify(option))
        },

        // 创建视频窗口
        create: function () {
            var rect = getRect.call(this)
            var _info = Object.assign({}, {}, rect)
            var windowSize = getWindowSize.call(this)
            var zoom = detectZoom()
            _info.isCustomDivision = isNaN(this.setting.division)
            _info.num = isNaN(this.setting.division) ? null : this.setting.division // 窗口数量
            _info.customDivision = isNaN(this.setting.division) ? JSON.parse(this.setting.division) : null
            _info.toolBar = this.setting.showBar ? 1 : 0 // 是否显示控制栏
            _info.windowType = this.setting.windowType - 0 // 判断当前为实时预览还是录像回放 0-实时预览  3-录像回放
            _info.clientAreaHeight = (windowSize.height * zoom) / 100
            _info.clientAreaWidth = (windowSize.width * zoom) / 100
            _info.authority = false // 操作栏上的按钮是否走权限判断(兼容视频子系统，需要传true，对外false)
            this.setTopBind = this.setTop.myBind(this)
            this.onbeforeunloadBind = this.onbeforeunload.myBind(this)
            this.visibilitychangeBind = this.setVisible.myBind(this)
            var that = this
            this.setLanguage() // 设置语言
            this.destroy().then(() => {
            this.send(
                {
                    method: 'window.create',
                    info: _info
                },
                {
                    onSuccess: function (data) {
                        console.log("chuangjian  chneggg ")
                        if (data.data && typeof data.data.hwnd === 'number') {
                            var hwnd = data.data.hwnd
                            console.log(`${[0, 2].includes(that.setting.windowType) ? '实时预览窗口' : '录像回放窗口'}创建成功 hwnd: ${hwnd}\n插件版本: ${window.dhPlayerControl.DHPlayerVersion}`);
                            that.setting.hwnd = hwnd
                            window.dhPlayerControl.videoList[that.setting.videoId].setting = that.setting
                            window.dhPlayerControl.hwndList[hwnd] = that.setting.videoId
                        }
                        that.setTopBind = that.setTop.myBind(that)
                        window.addEventListener('beforeunload', that.onbeforeunloadBind)
                        document.addEventListener('click', that.setTopBind)
                        that.handleAdjust()
                        let i = 0
                        while (i <= 3) {
                            that.setting.oldPosition = ''
                            that.setting.oldShield = ''
                            that.changePosition()
                            i++
                        }
                        that.setTabControlBtn()
                        that.setWindowDragEnable()
                        that.setWindowBorder(that.setting.showBorder)
                        // 初始化的时候手动调用show方法，避免出现播放器不显示的问题
                        // 前提条件： 保证在当前页面上时触发，否则不触发。
                        document.visibilityState === 'visible' ? that.show() : that.hide()
                        document.addEventListener('visibilitychange', that.visibilitychangeBind, true)
                        // 是否是插件内部鉴权，是的话则先登录
                        if(that.setting.usePluginLogin) {
                            if(window.dhPlayerControl.loginFlag === "LOGIN_SUCCESS") {
                                that.setting.createSuccess && that.setting.createSuccess(getVersionInfo())
                            } else if(window.dhPlayerControl.loginFlag === "LOGIN_ERROR"){
                                that.setting.createError && that.setting.createError({
                                    code: 1004,
                                    msg: "登录失败, 请检查登录信息",
                                    i18nKey: "dhplayer.login.failed"
                                })
                            }
                        }else {
                            that.setting.createSuccess && that.setting.createSuccess(getVersionInfo())
                        }
                    },
                    onError: function () {
                        // 断开连接
                        socketClose();
                        // 重连
                        let socketTimer = setTimeout(() => {
                            socketOpen();
                            clearTimeout(socketTimer)
                        }, 3000)
                        // that.setting.createError && that.setting.createError({
                        //     code: 1006,
                        //     data: null,
                        //     message: '插件创建失败，刷新重试',
                        //     success: false
                        // })
                    }
                }
            )
            })
        },

        /**
         * @name 设置语言
         * @param language zh-中文 en-英文
         * */
        setLanguage(language) {
            return new Promise((resolve, reject) => {
                this.send({
                    method: "common.updateLanguage",
                    info:{
                        LanguageType: language || this.setting.language || (localStorage.language === 'en' ? 'en' : 'zh') ,
                    }
                }, {
                    onSuccess: () => {
                        resolve()
                    }
                })
            })
        },
        // 设置播放器上方的操作按
        setTabControlBtn(btnList, snum) {
			// let showBtn = ["BTN_STREAM", "BTN_PTZ", "BTN_QUICKPLAY", "BTN_VOICE", "BTN_TALK", "BTN_RECORD", "BTN_PIC", "BTN_ENLARGE", "BTN_CLOSE"]
            let showBtn = ["BTN_STREAM", "BTN_VOICE", "BTN_TALK", "BTN_RECORD", "BTN_PIC", "BTN_ENLARGE", "BTN_CLOSE"]
            this.send({
                method: "video.toolbar.showButton",
                info: {
                    space: 15,
                    snum,
                    btns: btnList || showBtn
                }
            })
        },
        // 判断当前浏览器是否在tab页面上
        setVisible() {
            document.visibilityState == 'hidden' ? this.hide() : this.show()
        },
        //页面聚焦
        setTop() {
            this.focus = true
            document.visibilityState == 'visible' && this.browserFocusBlur()
        },
        browserFocusBlur: function () {
            this.send({
                method: 'browserFocusBlur',
                info: {
                    show: this.setting.show,
                    focus: this.focus
                }
            })
        },
        //刷新窗口位置
        handleAdjust: function () {
            var _this = this
            // 每帧执行一次，减少DHplayer延时
            !this.setting.stopRefresh && this.changePosition()
            !this.setting.stopRefresh && this.windowShield(this.cover())

            // 实时判断 dom 元素是否可见
            let el = document.getElementById(this.setting.videoId);
            if (this.setting.domVisible !== isDomVisible(el)) {
                this.setting.domVisible ? this.hide() : this.show()
                this.setting.domVisible = isDomVisible(el)
            }
            this.setting.refreshTimer = window.requestAnimationFrame(function () {
                return _this.handleAdjust()
            })
        },
        removeClickEventListener: function () {
            document.removeEventListener('click', this.setTopBind)
        },
        addClickEventListener: function () {
            document.addEventListener('click', this.setTopBind)
        },
        /**
         * 销毁当前播放器
         * @param isRefresh 表示页面刷新或者关闭情况
         * @return Promise对象
         */
        destroy: function (isRefresh = false) {
            return new Promise((resolve, reject) => {
                let that = window.dhPlayerControl.videoList[this.setting.videoId]
                if (!that || (that.setting && typeof that.setting.hwnd !== 'number')) {
                    resolve()
                } else {
                    this.send({
                        method: 'window.destroy',
                        info: {
                            hwnd: that.setting.hwnd,
                            isRefresh
                        }
                    }, {
                        onSuccess: () => {
                            document.removeEventListener('click', this.setTopBind)
                            document.removeEventListener('visibilitychange', this.visibilitychangeBind, true)
                            window.removeEventListener('beforeunload', this.onbeforeunloadBind)
                            console.log(`销毁成功, hwnd: ${that.setting.hwnd}`);
                            resolve()
                        },
                        onError: () => {
                            document.removeEventListener('click', this.setTopBind)
                            document.removeEventListener('visibilitychange', this.visibilitychangeBind, true)
                            window.removeEventListener('beforeunload', this.onbeforeunloadBind)
                            console.log(`销毁成功, hwnd: ${that.setting.hwnd}`);
                            resolve()
                        }
                    })
                }
            })
        },
        /**
         * 设置水印
         * @param { Object } option 参数
         * @param { Number } snum 窗口数量
         * @param { String } item.color 水印颜色
         * @param { Number } item.fontSize 水印尺寸
         * @param { Number } item.fontWeight 字体粗细
         * @param { String } item.position 水印位置
         * @param { Number } item.text 文本
         */
        waterMark: function (option) {
            option.forEach(item => {
                let rgb = item.color.split(',');
                let position = item.position.split(',');
                this.send({
                    method: 'video.setOSDInfo',
                    info: {
                        snum: item.snum,
                        R: rgb[0] || 255,
                        G: rgb[1] || 255,
                        B: rgb[2] || 255,
                        fontSize: item.fontSize || 14,
                        positionX: position[0] || 1,
                        positionY: position[1] || 1,
                        osdInfo: item.text || '',
                        fontWeight: item.fontWeight || 0
                    },
                })
            })
        },
        // 设置窗口是否支持拖拽
        setWindowDragEnable: function () {
            this.send({
                method: 'window.enableDrag',
                info: {
                    enable: this.setting.draggable
                }
            })
        },
        // 设置全屏
        setFullScreen: function () {
            this.send({
                method: 'video.fullScreen',
                info: {}
            })
        },
        /**
         * 设置窗口边框
         * @param {*} showBorder true-显示边框 false-不显示边框
         */
        setWindowBorder: function (showBorder) {
            this.setting.showBorder = showBorder
            this.send({
                method: 'window.videoWndFrameLine',
                info: {
                    enable: this.setting.showBorder
                }
            })
        },
        /**
         * @method chooseWindow 支持用户选择子窗口
         * @param { Number } snum 选择的子窗口，从0开始
         * @param { Function } cb 选中窗口回调
         */
        chooseWindow: function (snum, cb) {
            this.send({
                method: 'window.select',
                info: {
                    snum
                }
            })
            cb && cb(this.setting.channelList.filter(item => item.snum === snum)[0])
        },
        /**
         * @method openAudio 开启、关闭声音
         * @param { Number } option.isEnable 0-关闭，1-开启
         * @param { Number } option.snum 选择的子窗口，从0开始
         */
        openAudio: function (option) {
            this.send({
                method: 'video.enableAudio',
                info: {
                    snum: option.snum,
                    isEnable: option.isEnable,
                    videoType: Number(this.setting.windowType) === 0 ? 0 : 1, // 0-预览音频，1-回放音频
                }
            })
        },

        /**
         * @method startReal 实时预览集成
         * @param { Array } option
         * @param { String } item.channelId 通道Id （必传）
         * @param { String } item.channelName 通道名称(目前用于本地录像下载)
         * @param { Number } item.streamType 码流类型 1 主码流 2 辅码流 （默认主码流）
         * @param { Number } item.dataType 音视频类型  1-视频 2-音频 3-音视频 （默认视频）
         * @param { Number } item.deviceType 设备类别(用于对讲)
         * @param { Number } item.gbDevice 是否国标接入或国标级联(用于对讲)
         * @param { Number|String } item.cameraType 摄像头类型(用于云台)
         * @param { Number } item.capability 能力集(用于云台)
         * @param { Boolean } isReOpen 是否断线重连
         * @param { Boolean } isSubStream 主辅码流切换 true 是 false 否
         * @param { String } deviceCode: option.channelId.split('$1$0$')[0],
         * @param { String } deviceType: option.deviceType,
         * @param { Number } talkType: getTalkType(option.deviceType),
         */
        startReal: function (option, { isReOpen, isSubStream, count } = {}, type = 'real') {
            let tempList = []
            // 获取当前窗口分割数
            const totalWindows = this.setting.division;

            // 查找空闲窗口的函数
            const findFreeWindow = (channelList, totalWindows) => {
                // 如果是单窗口模式，直接返回窗口0
                if (totalWindows === 1) {
                    return 0;
                }

                // 多窗口模式下的原有逻辑
                let freeWindow = -1;
                for (let i = 0; i < totalWindows; i++) {
                    // 检查当前窗口是否被占用
                    let isWindowFree = true;
                    for (let j = 0; j < channelList.length; j++) {
                        if (channelList[j] && channelList[j].snum === i) {
                            isWindowFree = false;
                            break;
                        }
                    }
                    if (isWindowFree) {
                        freeWindow = i;
                        break;
                    }
                }
                return freeWindow;
            };

            // 切换窗口数
            let maxNum = option.map(item => item.snum + 1).sort((a, b) => b - a)[0]
            if (maxNum > 64) {
                this.setting.realError && this.setting.realError(item, {
                    code: 209,
                    i18nKey: 'video.player.support.max',
                    maxNum: maxNum,
                    message: '最大只支持64路播放'
                })
            }
            if (!isNaN(this.setting.division) && this.setting.division < maxNum) {
                this.changeDivision(maxNum)
            }
            option.forEach(item => {
                // 在这里查找空闲窗口
                const freeSnum = findFreeWindow(this.setting.channelList, totalWindows);

                if (freeSnum !== -1) {
                    // 如果找到空闲窗口，则使用该窗口号
                    item.snum = freeSnum;

                    let flag = false
                    this.setting.channelList = this.setting.channelList.map(realItem => {
                        if (realItem.snum === item.snum) {
                            flag = true
                            return { ...item, closed: true } // close标识位：表示外部主动删除
                        }
                        return realItem
                    })
                    if (!flag) {
                        tempList.push({ ...item })
                    }

                    // 如果有视频的关闭则优先关闭视频
                    let playVideo = () => {
                        // 如果是插件登录
                        if (this.setting.usePluginLogin) {
                            this.realByUrl({
                                ...item,
                                path: '',
                            }, { isReOpen, isSubStream, count }, true)
                            return
                        } else {
                            if (!this.setting.request[type]) {
                                this.setting.realError && this.setting.realError(item, {
                                    code: 207,
                                    i18nKey: 'video.player.please.afferent.interface.real',
                                    message: '请通过 request 属性传入实时预览接口'
                                })
                                return
                            }
                            this.setting.request[type](getAjaxParam(item, type)).then(res => {
                                if (res.url) {
                                    this.realByUrl({
                                        ...item,
                                        path: dealUrl(res),
                                    }, { isReOpen, isSubStream, count }, true)
                                }
                            }).catch(err => {
                                // 播放失败时，将该通道从channelList中移除
                                this.setting.channelList = this.setting.channelList.filter(realItem => !(realItem.snum === item.snum && realItem.channelId === item.channelId))
                                this.setting.realError && this.setting.realError(item, err)
                            })
                        }
                    }

                    if (!isReOpen && !isSubStream && flag) {
                        this.closeVideo(item.snum).then(() => {
                            playVideo()
                        })
                        return
                    }
                    playVideo()
                } else {
                    // 没有找到空闲窗口，这里可以添加您的处理逻辑
                    console.warn(`没有找到空闲窗口来播放通道: ${item.channelName || item.channelId}`);
                    // 例如，您可以选择跳过播放，或者覆盖某个窗口
                    // 目前我选择跳过该通道的播放
                    this.setting.realError && this.setting.realError(item, {
                        code: -1, // 可以自定义一个错误码
                        i18nKey: 'video.player.no.free.window',
                        message: '没有空闲窗口可用'
                    });
                }
            })
            this.setting.channelList = [...this.setting.channelList, ...tempList]
            // 强绑定
            window.dhPlayerControl.videoList[this.setting.videoId].setting.channelList = [...this.setting.channelList]
        },
        /**
         * @method realByUrl 通过rtsp流地址进行实时预览
         * @param { Number } option.snum 选择的子窗口，从0开始
         * @param { String } option.channelId 通道id
         * @param { String } option.channelName 通道名称(目前用于本地录像下载)
         * @param { String } option.path rtsp地址
         * @param { Boolean } option.redirect 重定向，默认false （拼接地址需要改为true，接口返回地址为false）
         * @param { String } option.cameraType 云台使用，相机类型 【暂不使用】
         * @param { Number } option.decodeMode 解码模式 软解-0 硬解-1 快速硬解-2 [默认快速硬解]
         * @param { Boolean } isSubStream 是否为主辅码流切换，true 表示是 false表示否
         * @param { Boolean } isReOpen 是否为实时预览断线重连
         * @param { Boolean } count 当前是第几次重连
         */
        realByUrl: function (option, { isSubStream, isReOpen, count } = {}, isProj) {
            let sendVideo = () => {
                this.send({
                    method: 'video.realmonitor',    
                    info: {
                        snum: option.snum,
                        path: option.path,
                        channelId: option.channelId,
                        channelName: option.channelName || '',
                        redirect: typeof option.redirect === 'boolean' ? option.redirect : false,
                        camerType: option.cameraType,
                        decodeMode: typeof option.decodeMode === 'number' ? option.decodeMode : 2,
                        bStreamChange: !!isSubStream,
                        reopenvideo: !!isReOpen,
                        streamType: option.streamType || 1,
                        count,
                        deviceCode: option.channelId && option.channelId.split('$1$0$')[0] || '',
                        deviceType: option.deviceType,
                        talkType: getTalkType(option.deviceType),
                    }
                })
            }
            // 非集成情况
            if (!isProj) {
                let index = this.setting.channelList.findIndex(item => item.snum === option.snum)
                if (index >= 0) {
                    this.setting.channelList[index] = { ...option, byUrl: true, closed: true }
                } else {
                    this.setting.channelList.push({ ...option, byUrl: true })
                }
                // 强绑定
                window.dhPlayerControl.videoList[this.setting.videoId].setting.channelList = [...this.setting.channelList];
                if (!isReOpen && !isSubStream) {
                    this.closeVideo(option.snum).then(() => {
                        sendVideo()
                    })
                }
                return
            }
            let timer = setInterval(() => {
                clearInterval(timer)
                if(this.setting.hwnd >= 0) {
                    sendVideo()
                } else {
                    this.realByUrl(option, { isSubStream, isReOpen, count }, isProj)                    
                }
            }, 200)
        },
        /**
         * @method startTalk 对讲集成
         * @param { Number } snum 选择的子窗口，从0开始
         */
        startTalk: async function (snum = 0, type = 'talk') {
            let talkIndex = this.setting.channelList.findIndex(item => item.snum === snum)
            if (talkIndex < 0) {
                return this.setting.talkError(talkParam, {
                    code: 206,
                    i18nKey: 'video.player.current.window.no.live.view',
                    message: '请先调用实时预览接口'
                })
            }
            let talkParam = this.setting.channelList[talkIndex]
            let param = getAjaxParam(talkParam, type).data
            if (!this.setting.request[type]) {
                this.setting.talkError && this.setting.talkError(talkParam, {
                    code: 207,
                    i18nKey: 'video.player.please.afferent.interface.talk.and.stopTalk',
                    message: '请通过 request 属性传入对讲接口和停止对讲接口'
                })
                return
            }
            this.setting.request[type]({ data: param }).then(res => {
                talkParam.session = res.session
                // 保证所有参数都统一
                let { audioBit, audioType, sampleRate } = res
                talkParam.isTalk = true
                this.talkByUrl({
                    redirect: false,
                    audioBit,
                    audioType,
                    sampleRate,
                    path: dealUrl(res),
                    channelId: talkParam.channelId,
                    talkType: getTalkType(talkParam.deviceType),
                    gbDevice: typeof res.gbDevice !== undefined && Boolean(res.gbDevice) || Boolean(talkParam.gbDevice),
                    snum
                })
            }).catch(err => {
                this.setting.talkError && this.setting.talkError(talkParam, err)
            })
        },

        /**
         * @method talkByUrl 通过rtsp流进行对讲
         * @param { Number } option.snum 窗口号
         * @param { String } option.channelId 通道id
         * @param { String } option.path rtsp地址
         * @param { Number } option.audioType 音频类型 0-default 1-PCM 2-G711a 3-AMR 4-G711U 5-G726 6-AAC 7-G722 8-G711
         * @param { Number } option.audioBit 位数 8 、16
         * @param { Number } option.sampleRate 采样频率 8000、16000、32000、48000、8192
         * @param { Number } option.talkType 对讲类型 1-设备 2-通道
         * @param { Boolean } option.gbDevice 是否是国标设备
         */
        talkByUrl: function (option) {
            // 发送对讲
            this.send({
                method: 'video.starttalk',
                info: {
                    snum: option.snum,
                    path: option.path,
                    channelId: option.channelId,
                    redirect: false, // 写死
                    audioType: option.audioType,
                    audioBit: option.audioBit,
                    sampleRate: option.sampleRate,
                    talkType: option.talkType,
                    gbDevice: option.gbDevice
                }
            })
        },

        /**
         * @method startPlayback 录像回放集成
         * @param { Array } option
         * @param { String } item.channelId 通道Id
         * @param { String } item.channelName 通道名称(目前用于本地录像下载)
         * @param { String } item.name 通道名称
         * @param { Number } item.streamType 码流类型 0 所有码流 1 主码流 2 辅码流 （默认所有码流）
         * @param { String } item.startTime 开始时间 '2022-10-26 00:00:00'
         * @param { String } item.endTime 结束时间 '2022-10-26 23:59:59'
         * @param { Number } item.recordSource 录像类型 2-设备录像 3-中心录像
         * @param { Number } option.snum 窗口号
         * @param { Boolean } isConnect 是否拖拽/播放下一段录像
         * @param { Boolean } bContinue 是否为播放下一段录像(用于告诉客户端)
         * @param { Boolean } scaleSteps 当前录像进度条的显示状态
         */
        startPlayback: function (option, { isConnect, bContinue, scaleSteps } = {}) {
            let timeFormatter = (time) => {
                return parseInt(new Date(time).getTime() / 1000)
            }
            let getPlayBackRtsp = (param) => {
                if (Number(param.recordSource) === 3 || Number(param.recordSource === 4)) {
                    // 中心录像-按文件
                    getPlayBackRtspByFile.call(this, param, isConnect).then(res => {
                        if (res.code === 201) {
                            return this.setting.playbackError && this.setting.playbackError(param, res)
                        }
                        param.endTime = Number(param.records[param.records.length - 1].endTime)
                        this.playbackByUrl({
                            ...param,
                            path: res.rtspUrl,
                            records: res.records,
                            redirect: false,
                            bContinue,
                            scaleSteps,
                            isLastFile: param.currentIndex === res.records.length - 1
                        }, true)
                    }).catch(err => {
                        this.setting.playbackError && this.setting.playbackError(param, err)
                    })
                } else if (Number(param.recordSource) === 2) {
                    // 设备录像-按时间
                    getPlayBackRtspByTime.call(this, param).then(res => {
                        param.endTime = Number(param.records[param.records.length - 1].endTime)
                        this.playbackByUrl({
                            ...param,
                            path: res.rtspUrl,
                            records: res.records,
                            redirect: false,
                            bContinue,
                            scaleSteps,
                            isLastFile: param.currentIndex === res.records.length - 1
                        }, true)
                    }).catch(err => {
                        this.setting.playbackError && this.setting.playbackError(param, err)
                    })
                } else if (Number(param.recordSource) === 1) {
                    // 自动识别
                    queryRecord.call(this, param)
                        .then(() => {
                            let recordSource = param.records[0].recordSource
                            param.recordSource = Number(recordSource)
                            if (Number(recordSource) === 3 || Number(recordSource) === 4) {
                                // 中心录像-按文件
                                getPlayBackRtspByFile.call(this, param, isConnect).then(res => {
                                    if (res.code === 201) {
                                        return this.setting.playbackError && this.setting.playbackError(param, res)
                                    }
                                    param.endTime = Number(param.records[param.records.length - 1].endTime)
                                    this.playbackByUrl({
                                        ...param,
                                        path: res.rtspUrl,
                                        records: res.records,
                                        redirect: false,
                                        bContinue,
                                        scaleSteps,
                                        isLastFile: param.currentIndex === res.records.length - 1
                                    }, true)
                                }).catch(err => {
                                    this.setting.playbackError && this.setting.playbackError(param, err)
                                })
                            }
                            if (Number(recordSource) === 2) {
                                // 设备录像-按时间
                                getPlayBackRtspByTime.call(this, param).then(res => {
                                    param.endTime = Number(param.records[param.records.length - 1].endTime)
                                    this.playbackByUrl({
                                        ...param,
                                        path: res.rtspUrl,
                                        records: res.records,
                                        redirect: false,
                                        bContinue,
                                        scaleSteps,
                                        isLastFile: param.currentIndex === res.records.length - 1
                                    }, true)
                                }).catch(err => {
                                    this.setting.playbackError && this.setting.playbackError(param, err)
                                })
                            }
                        }).catch(err => {
                            this.setting.playbackError && this.setting.playbackError(param, err)
                        })
                } else {
                    this.setting.playbackError && this.setting.playbackError(
                        param,
                        {
                            code: 404,
                            i18nKey: 'video.player.only.play.device.and.center.recordings',
                            message: '只能播放设备录像和中心录像！'
                        })
                }
            }
            if (isConnect) {
                getPlayBackRtsp(option)
            } else {
                let channelList = []
                // 切换窗口数
                let maxNum = option.map(item => item.snum + 1).sort((a, b) => b - a)[0]
                if (!isNaN(this.setting.division) && this.setting.division < maxNum) {
                    this.changeDivision(maxNum)
                }
                option.forEach(item => {
                    // 对时间做格式化处理
                    item.startTime = timeFormatter(item.startTime)
                    item.endTime = timeFormatter(item.endTime)
                    // 如果开始时间大于结束时间，则调换位置
                    if (item.startTime > item.endTime) {
                        let tempTime = item.startTime
                        item.startTime = item.endTime
                        item.endTime = tempTime
                    }
                    item.bBack = 0
                    let flag = true
                    this.setting.channelList.forEach((oItem, oIndex) => {
                        if (oItem.snum === item.snum) {
                            flag = false
                            this.setting.channelList[oIndex] = { ...item, closed: true }
                            this.closeVideo(item.snum).then(() => {
                                // 播放视频
                                if (this.setting.usePluginLogin) {
                                    this.playbackByUrl({
                                        ...item,
                                        path: '',
                                        records: [],
                                        redirect: false,
                                        bContinue,
                                        scaleSteps
                                    }, true)
                                } else {
                                    getPlayBackRtsp({ ...item })
                                }
                            })
                        }
                    })
                    if (flag) {
                        channelList.push({ ...item })
                    }
                })
                channelList.forEach(item => {
                    if (this.setting.usePluginLogin) {
                        this.playbackByUrl({
                            ...item,
                            path: '',
                            records: [],
                            redirect: false,
                            bContinue,
                            scaleSteps
                        }, true)
                    } else {
                        getPlayBackRtsp({ ...item })
                    }
                })
                this.setting.channelList = [...this.setting.channelList, ...channelList]
                // 强绑定
                window.dhPlayerControl.videoList[this.setting.videoId].setting.channelList = [...this.setting.channelList]
            }
        },

        /**
         * @method playbackByUrl 通过rtsp录像回放
         * @param { Number } option.snum 选择的子窗口，从0开始
         * @param { String } option.channelId 通道id
         * @param { String } option.channelName 通道名称(目前用于本地录像下载)
         * @param { String } option.path rtsp地址
         * @param { Array } option.records 包含某个时间段的录像文件信息
         * @param { Date } option.startTime 时间相关均为时间戳（new Date().getTime() / 1000）
         * @param { Date } option.endTime 时间相关均为时间戳（new Date().getTime() / 1000）
         * @param { Number } option.recordSource 录像类型 2-设备录像 3-中心录像 4-统一云
         * @param { Number } option.decodeMode 解码模式 软解-0 硬解-1 快速硬解-2 [默认快速硬解]
         * @param { Boolean } option.redirect 重定向，默认false （拼接地址需要改为true，接口返回地址为false）
         * @param { Boolean } option.bContinue 是否继续播放录像 true-是  false-否
         * @param { Boolean } bContinue 是否续播
         * @param { Number } scaleSteps 录像进度条样式
         * @param { Boolean } isLastFile 是否为最后一段录像？
         */
        playbackByUrl: function (option, isProj) {
            // 非集成情况
            if (!isProj) {
                let index = this.setting.channelList.findIndex(item => item.snum === option.snum)
                if (index >= 0) {
                    this.setting.channelList[index] = { ...option, byUrl: true }
                } else {
                    this.setting.channelList.push({ ...option, byUrl: true })
                }
            }
            this.send({
                method: 'video.playback',
                info: {
                    snum: option.snum,
                    path: option.path,
                    records: option.records,
                    startTime: option.startTime,
                    endTime: option.endTime,
                    recordSource: option.recordSource,
                    playStartTime: option.playStartTime || option.startTime,
                    playEndTime: option.playEndTime || option.endTime,
                    currentPlayTime: option.currentPlayTime || option.playStartTime,
                    channelId: option.channelId,
                    channelName: option.channelName || option.name || '',
                    decodeMode: typeof option.decodeMode === 'number' ? option.decodeMode : 2,
                    redirect: typeof option.redirect === 'boolean' ? option.redirect : false,
                    bBack: option.bBack || 0,
                    bContinue: !!option.bContinue,
                    scaleSteps: option.scaleSteps || 0,
                    lastFile: !!option.isLastFile
                }
            })
        },
        /**
         * @method startDownloadRecord 开始下载录像-集成
         * @param {*} option 参数同 startPlayback 方法
         */
        startDownloadRecord: function (option) {
            let timeFormatter = (time) => {
                return parseInt(new Date(time).getTime() / 1000)
            }
            let getPlayBackRtsp = (param) => {
                if (Number(param.recordSource) === 3 || Number(param.recordSource) === 4) {
                    // 中心录像-按文件
                    getPlayBackRtspByFile.call(this, param).then(res => {
                        if (res.code === 201) {
                            return this.setting.downloadError && this.setting.downloadError(param, res)
                        }
                        param.endTime = Number(param.records[param.records.length - 1].endTime)
                        this.downloadRecord({
                            snum: param.snum,
                            url: res.rtspUrl,
                            records: res.records,
                            startTime: param.startTime,
                            endTime: param.endTime,
                        })
                    }).catch(err => {
                        this.setting.downloadError && this.setting.downloadError(param, err)
                    })
                } else if (Number(param.recordSource) === 2) {
                    // 设备录像-按时间
                    getPlayBackRtspByTime.call(this, param).then(res => {
                        param.endTime = Number(param.records[param.records.length - 1].endTime)
                        this.downloadRecord({
                            snum: param.snum,
                            url: res.rtspUrl,
                            records: res.records,
                            startTime: param.startTime,
                            endTime: param.endTime,
                        })
                    }).catch(err => {
                        this.setting.downloadError && this.setting.downloadError(param, err)
                    })
                } else {
                    this.setting.downloadError && this.setting.downloadError(
                        param,
                        {
                            code: 404,
                            i18nKey: 'video.player.only.download.device.and.center.recordings',
                            message: '只能下载设备录像和中心录像！'
                        })
                }
            }
            if (option.length > 4) {
                return this.setting.downloadError({
                    code: 401,
                    i18nKey: 'video.player.download.recordings.max',
                    message: "最多支持4路录像同时下载"
                });
            }
            option.forEach(item => {
                // 对时间做格式化处理
                item.startTime = timeFormatter(item.startTime)
                item.endTime = timeFormatter(item.endTime)
                // 如果开始时间大于结束时间，则调换位置
                if (item.startTime > item.endTime) {
                    let tempTime = item.startTime
                    item.startTime = item.endTime
                    item.endTime = tempTime
                }
                item.bBack = 0
                getPlayBackRtsp(item)
            })
        },
        /**
         * 处理DHPlayer位置
         * @param {*} option
         * @param {*} callBack
         * @returns
         */
        changePosition: function (option, callBack) {
            var windowSize = getWindowSize.call(this)
            var zoom = detectZoom()
            var rect = getRect.call(this)
            for (let i in rect) {
                rect[i] = (rect[i] * zoom) / 100
            }
            var _info = Object.assign({}, {}, rect, option)
            _info.clientAreaHeight = (windowSize.height * zoom) / 100
            _info.clientAreaWidth = (windowSize.width * zoom) / 100
            _info.browserScreenX = getScreenX()
            // 暂时用不到
            _info.screenX = window.screenX
            _info.screenY = window.screenY
            // 火狐需要传的内容
            if (!window.dhPlayerControl.isPIframe) {
                _info.mozInnerScreenX = (window.top.mozInnerScreenX * zoom) / 100
                _info.mozInnerScreenY = (window.top.mozInnerScreenY * zoom) / 100
            } else {
                _info.mozInnerScreenX = (this.setting.topMozInnerScreenX * zoom) / 100
                _info.mozInnerScreenY = (this.setting.topMozInnerScreenY * zoom) / 100
            }
            delete _info.width
            delete _info.height
            _info.show = document.visibilityState === 'hidden' ? false : true
            _info.title = window.dhPlayerControl.isPIframe ? this.setting.documentTitle : window.top.document.title
            let sendPosition = () => {
                this.send(
                    {
                        method: 'window.change',
                        info: _info
                    },
                    callBack
                )
            }
            // 位置改变后就触发遮挡
            if (this.setting.oldPosition === JSON.stringify(_info)) {
                // 位置固定后，改变三次位置，强制触发三次遮挡，处理位置偏移问题。
                while (this.adjustCount < 3) {
                    this.adjustCount++
                    sendPosition()
                    this.windowShield(this.cover(), true)
                }
                return
            }
            // 位置发生改变，强制触发遮挡事件
            this.adjustCount = 0
            this.setting.oldPosition = JSON.stringify(_info)
            sendPosition()
        },
        // 隐藏视频
        hide: function () {
            this.setting.show = false
            this.setting.stopRefresh = true
            this.send({
                method: 'window.show',
                info: {
                    show: false
                }
            })
        },
        //显示视频
        show: function () {
            var that = this
            this.setting.stopRefresh = false
            this.setting.show = true
            this.send({
                method: 'window.show',
                info: {
                    show: true
                }
            },
                {
                    onSuccess: function () {
                        if (that.setting.refreshTimer) {
                            window.cancelAnimationFrame(that.setting.refreshTimer)
                        }
                        that.setting.oldPosition = ""
                        that.handleAdjust()
                        that.setting.showWindowSuccess && that.setting.showWindowSuccess()
                    },
                    onError: this.setting.showWindowError
                }
            )
        },

        /**
         * @method downloadRecord 录像下载
         * @param { Object } option
         * @param { Number } option.snum 选择的子窗口，从0开始
         * @param { String } option.url 下载地址
         * @param { Array } option.records 包含某个时间段的录像文件信息
         * @param { Number } option.startTime 时间相关均为时间戳,具体参考大华播放控件开发手册
         * @param { Number } option.endTime 时间相关均为时间戳,具体参考大华播放控件开发手册
         * @param { Boolean } option.redirect 默认 false
         */
        downloadRecord: function (option) {
            this.send({
                method: 'video.downloadByTime',
                info: {
                    channelId: option.channelId,
                    snum: option.snum,
                    url: option.url,
                    records: option.records,
                    startTime: option.startTime,
                    endTime: option.endTime,
                    redirect: typeof option.redirect === 'boolean' ? option.redirect : false
                }
            })
        },
        /**
         * @method closeVideo 关闭指定窗口视频或全部关闭
         * @param { Number } option.snum 选择的子窗口, 不传默认全部关闭
         */
        closeVideo: function (snum) {
            return new Promise((resolve) => {
                this.send({
                    method: 'video.close',
                    info: {
                        snum: typeof snum === 'number' ? snum : 0,
                        isAll: typeof snum === 'number' ? false : true
                    }
                }, {
                    onSuccess: function() {
                        resolve()
                    }
                })
            })
        },
        /**
         * @method closeTalk 关闭对讲
         */
        closeTalk: function () {
            this.send({
                method: 'video.closetalk',
                info: {
                    snum: 0,
                    isAll: true
                }
            })
        },
        /**
         * @method continuePlayback 操作录像
         * @param { Number } option.snum 选择的子窗口，从0开始
         * @param { Number } option.state 窗口状态：0-暂停，1-继续
         */
        controlPlayback: function (option) {
            this.send({
                method: 'video.playbackChangeState',
                info: {
                    snum: option.snum,
                    state: option.state
                }
            })
        },

        //显示下方控制栏, show: true-显示，false-隐藏
        showControlBar: function (show = true) {
            this.setting.showBar = show
            this.send({
                method: 'video.setToolBarShow',
                info: {
                    isShow: show ? 1 : 0
                }
            })
        },
        /**
         * @method continuePlayback 本地录像下载
         * @param { Number } snum 选择的子窗口，从0开始
         */
        localRecordDownload: function (snum) {
            this.send({
                method: 'vidoe.localRecord',
                info: {
                    snum
                }
            })
        },
        /**
         * @method video.division.change 切换当前控件展示的窗口数量
         * @param {*} division 当前控件展示的窗口数量
         * @param {String} 分割窗口数类型 normal-正常 custom-自定义
         * @desc 代码手动调用该方法没有提供回调，此处添加回调
         */
        changeDivision: function (info) {
            if (info) {
                // 自定义的情况
                if (isNaN(info)) {
                    this.setting.division = info
                    this.send({
                        method: 'video.customDivision.change',
                        info: JSON.parse(info)
                    })
                    this.setting.changeDivision && this.setting.changeDivision(info)
                } else {
                    this.setting.division = Number(info)
                    this.send({
                        method: "video.division.change",
                        info: {
                            "division": this.setting.division
                        }
                    })
                    this.setting.changeDivision && this.setting.changeDivision(this.setting.division)
                }
                this.setWindowDragEnable()
            }
        },
        //窗口抓图
        snapshot: function (snum) {
            this.send({
                method: 'video.snapic',
                info: {
                    snum
                }
            })
        },
        // 视频被遮挡处理
        windowShield: function (option, flag, callBack) {
            var windowSize = getWindowSize.call(this)
            var zoom = detectZoom()
            var _info = {}
            _info.region = this.getShieldRect(option).map(item => item * zoom / 100)
            _info.clientAreaHeight = (windowSize.height * zoom) / 100
            _info.clientAreaWidth = (windowSize.width * zoom) / 100
            if (!flag && this.setting.oldShield === JSON.stringify(_info)) {
                return
            }
            this.setting.oldShield = JSON.stringify(_info)
            this.send(
                {
                    method: 'window.shield',
                    info: _info
                },
                callBack
            )
        },
        // 视频插件版本号
        version: function (callBack) {
            this.send({
                method: 'common.version',
                info: {}
            },
                callBack
            )
        },
        /**
         * 视频是否显示规划线
         * @param { Object } option
         * @param { Number } snum 窗口号
         * @param { isEnableIVS } 是否显示规则框 true-显示 false-隐藏
         * @param { ivsType } 规则框类型 1-智能规则框，2-智能目标框 (不传默认为1) 7-辅助帧
         */
        isEnableIvs: function (option) {
            this.send({
                method: 'video.enableIvs',
                info: {
                    snum: option.snum,
                    isEnableIVS: option.isEnableIVS,
                    ivsType: option.ivsType
                },
            })
        },

        getWindowState: function (callBack) {
            this.send({
                method: 'window.getWindowState',
                info: {},
            },
                callBack
            )
        },
        // 防止插件超出浏览器显示
        cover: function () {
            var rect = getRect.call(this)
            var left = rect.left,
                top = rect.top,
                right = rect.right,
                bottom = rect.bottom,
                width = rect.width,
                height = rect.height,
                arr = [],
                windowSize = getWindowSize.call(this)

            let shieldFn = (domSize) => {
                if (domSize && domSize[0]) {
                    // 超过上方则遮挡
                    if (top < domSize[0].top) {
                        arr.push(left, top, width + 1, domSize[0].top - top)
                    }
                    // 超过下方则遮挡
                    if (bottom > domSize[0].bottom) {
                        arr.push(left, domSize[0].bottom, width + 1, bottom - domSize[0].bottom)
                    }
                    // 左侧遮挡
                    if (left < domSize[0].left) {
                        arr.push(left, top, domSize[0].left - left + 1, height)
                    }
                    // 右侧遮挡
                    if (right > domSize[0].right) {
                        arr.push(domSize[0].right, top, right - domSize[0].right + 1, height)
                    }
                }
            }
            // 处理有iframe下， 超出最外侧body遮挡的问题
            shieldFn([
                {
                    top: 0,
                    left: 0,
                    right: windowSize.width,
                    bottom: windowSize.height
                }
            ])
            // 处理当前window下的遮挡问题
            let pOutContent, outLeft, outTop
            if (window.dhPlayerControl.isPIframe) {
                pOutContent = this.setting.pIframeRect
                outLeft = pOutContent.left
                outTop = pOutContent.top
            } else {
                var iframes = window.parent.document.getElementsByTagName('iframe')
                var pIframe = null
                var dom = null
                let getpIframe = (index) => {
                    if (!iframes[index]) return
                    try {
                        dom = iframes[index].contentWindow.document.getElementById(this.setting.videoId)
                        if (dom) {
                            pIframe = iframes[index]
                        } else {
                            getpIframe(index + 1)
                        }
                    } catch (err) {
                        getpIframe(index + 1)
                    }
                }
                iframes.length && getpIframe(0)
                if (pIframe) {
                    pOutContent = pIframe.getBoundingClientRect()
                }
            }
            pOutContent && shieldFn([{
                top: pOutContent.top,
                left: pOutContent.left,
                right: pOutContent.left + pOutContent.width,
                bottom: pOutContent.top + pOutContent.height
            }])
            // 处理DOM元素遮挡问题，主要用于滚动页面，超出隐藏的问题
            if (this.setting.coverShieldClass && this.setting.coverShieldClass.length) {
                this.setting.coverShieldClass.forEach((item) => {
                    let dom = document.getElementsByClassName(item)[0]
                    let domSize = dom ? dom.getClientRects() : null
                    domSize = [
                        {
                            left: domSize[0].left,
                            top: domSize[0].top,
                            bottom: domSize[0].bottom,
                            right: domSize[0].right
                        }
                    ]
                    if (pOutContent) {
                        domSize[0].left = domSize[0].left + pOutContent.left
                        domSize[0].top = domSize[0].top + pOutContent.top
                        domSize[0].bottom = domSize[0].top + domSize[0].height
                        domSize[0].right = domSize[0].left + domSize[0].width
                    }
                    domSize && shieldFn(domSize)
                })
            }
            return arr
        },

        /**
         * @desc 登录平台
         * @param {String} host ip
         * @param {String} port 端口
         * @param {String} username 用户名
         * @param {String} password 密码
         * */
        loginServer: function () {
            this.send({
                method: 'window.loginServer',
                info: {
                    host: this.setting.pluginLoginInfo.host,
                    port: String(this.setting.pluginLoginInfo.port), // 自动转换为字符串
                    username: this.setting.pluginLoginInfo.username,
                    password: this.setting.pluginLoginInfo.password
                }
            })

        },

        //  退出登录
        logoutServer: function () {
            return new Promise((resolve, reject) => {
                this.send({
                    method: 'window.logoutServer',
                    info: {}
                })
                setTimeout(() => {
                    resolve()
                }, 2000)
            })
        },

        // 获取登录状态信息
        getLoginState: function () {
            this.send({
                method: 'window.getLoginState',
                info: {},
            },
            )
        },

        // 获取登录信息
        getLoginInfo: function () {
            this.send({
                method: 'window.getLoginInfo',
                info: {},
            },
            )
        },

        // 扩展方法
        extendOption: function (ids, option) {
            var map = {}
            for (var i = 0; i < ids.length; i++) {
                map[ids[i]] = this.setting[ids[i]]
            }
            return Object.assign({}, map, option)
        },
        //遮挡部分位置获取
        getShieldRect: function (option) {
            // 获取位置信息
            var shieldClass = this.setting.shieldClass || [],
                arr = option || []
            for (var i = 0; i < shieldClass.length; i++) {
                arr = [...arr, ...computedRect.call(this, shieldClass[i])]
            }
            if (window.dhPlayerControl.isPIframe) {
                arr.push(...this.setting.pIframeShieldData)
            } else {
                var parentIframeShieldClass = this.setting.parentIframeShieldClass || []
                for (i = 0; i < parentIframeShieldClass.length; i++) {
                    arr = [...arr, ...computedRect.call(this, parentIframeShieldClass[i], true)]
                }
            }
            return arr
        },
        MainCall: function (option) {
            var dom = document.getElementById(this.setting.ieDom)
            this.setting.option_id[option.id] = option
            dom && dom.MainCall(option.method, JSON.stringify(option))
        },
        init: function () {
            // 判断是不是跨域
            try {
                // 不是跨域
                console.log(window.top.origin)
                window.dhPlayerControl.isPIframe = false
                this.initPlayer()
            } catch (err) {
                // 是跨域
                console.log("存在跨域iframe")
                window.dhPlayerControl.isPIframe = true
                addEventListener('message', e => {
                    if (e.data.methods === 'title') {
                        this.setting.documentTitle = e.data.title || document.title
                    }
                    if (e.data.methods === 'rect') {
                        this.setting.topInnerWidth = e.data.topInnerWidth || 0
                        this.setting.topInnerHeight = e.data.topInnerHeight || 0
                        this.setting.pIframeRect = e.data.pIframeRect || 0
                        this.setting.topMozInnerScreenX = e.data.topMozInnerScreenX || 0
                        this.setting.topMozInnerScreenY = e.data.topMozInnerScreenY || 0
                    }
                    if (e.data.methods == 'shieldRect') {
                        this.setting.pIframeShieldData = e.data.pIframeShieldData || []
                    }
                })
                this.initPlayer()
            }
        },
        initPlayer: function () {
            let hwnd = ''
            if (window.dhPlayerControl.videoList[this.setting.videoId]) {
                hwnd = window.dhPlayerControl.videoList[this.setting.videoId].setting.hwnd
            }
            window.dhPlayerControl.videoList[this.setting.videoId] = this
            this.setting.hwnd = hwnd
            if (!window.dhPlayerControl.wsConnect) {
                window.dhPlayerControl.wsConnect = true
                socketOpen.call(this)
            } else {
                if (window.dhPlayerControl.windowState === 'wsSuccess' && (!this.setting.usePluginLogin || window.dhPlayerControl.loginFlag !== 'LOGIN_PENDING')) {
                    // 建立连接后, 确保每次初始化是否支持
                    let _isSupport = isSupport()
                    if (!_isSupport.success) {
                        return this.setting.createError && this.setting.createError(_isSupport)
                    }
                    if (this.setting.socketTimer) {
                        clearTimeout(this.setting.socketTimer)
                    };
                    this.setting.socketTimer = setTimeout(() => {
                        this.create()
                    }, this.setting.usePluginLogin ? 1000 : 300)
                } else if (window.dhPlayerControl.windowState === "noPlugin") {
                    this.setting.createError && this.setting.createError({
                        code: 1001,
                        success: false,
                        i18nKey: 'video.player.plugin.not.installed',
                        message: "插件未安装"
                    })
                } else if(window.dhPlayerControl.windowState === "wsError") {
                    
                    this.setting.createError && this.setting.createError({
                        code: 1003,
                        success: false,
                        message: "无法与播放器建立连接" + window.isResetConnect ? ', 正在重连...' : ""
                    })
                } else {
                    setTimeout(() => {
                        this.initPlayer()
                    }, 300)
                }
            }
            this.setting.browserType = broswerInfo()
        }
    }
    window.VideoPlayer = window.VideoPlayer || VideoPlayer
})()